/*********************************************************************************** * Copyright (c) 2013. Nickolay Gerilovich. Russia. * Some Rights Reserved. ************************************************************************************/ package com.github.nickvl.xspring.core.log.aop; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.EnumMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.logging.Log; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.util.ReflectionUtils; /** * Logger aspect. */ @Aspect public class AOPLogger implements InitializingBean { // private static final Log LOGGER = LogFactory.getLog(AOPLogger.class); private LogAdapter logAdapter; private Map<Severity, LogStrategy> logStrategies; private final LocalVariableTableParameterNameDiscoverer localVariableNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); private final ExceptionResolver exceptionResolver = new ExceptionResolver(); private final ConcurrentMap<Method, MethodDescriptor> cache = new ConcurrentHashMap<Method, MethodDescriptor>(); @Override public void afterPropertiesSet() throws Exception { logStrategies = new EnumMap<Severity, LogStrategy>(Severity.class); logStrategies.put(Severity.FATAL, new LogStrategy.FatalLogStrategy(logAdapter)); logStrategies.put(Severity.ERROR, new LogStrategy.ErrorLogStrategy(logAdapter)); logStrategies.put(Severity.WARN, new LogStrategy.WarnLogStrategy(logAdapter)); logStrategies.put(Severity.INFO, new LogStrategy.InfoLogStrategy(logAdapter)); logStrategies.put(Severity.DEBUG, new LogStrategy.DebugLogStrategy(logAdapter)); logStrategies.put(Severity.TRACE, new LogStrategy.TraceLogStrategy(logAdapter)); } public void setLogAdapter(LogAdapter log) { this.logAdapter = log; } /** * Advise. Logs the advised method. * * @param joinPoint represents advised method * @return method execution result * @throws Throwable in case of exception */ @Around(value = "execution(@(@com.github.nickvl.xspring.core.log.aop.annotation.Logging *) * *.* (..))" // per method + " || execution(* (@(@com.github.nickvl.xspring.core.log.aop.annotation.Logging *) *).*(..))" // per class , argNames = "joinPoint") public Object logTheMethod(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); Log logger = logAdapter.getLog(joinPoint.getTarget().getClass()); Method method = extractMethod(joinPoint); MethodDescriptor descriptor = getMethodDescriptor(method); InvocationDescriptor invocationDescriptor = descriptor.getInvocationDescriptor(); String methodName = method.getName(); if (beforeLoggingOn(invocationDescriptor, logger)) { ArgumentDescriptor argumentDescriptor = getArgumentDescriptor(descriptor, method, args.length); logStrategies.get(invocationDescriptor.getBeforeSeverity()).logBefore(logger, methodName, args, argumentDescriptor); } Object result; if (invocationDescriptor.getExceptionAnnotation() == null) { result = joinPoint.proceed(args); } else { try { result = joinPoint.proceed(args); } catch (Exception e) { ExceptionDescriptor exceptionDescriptor = getExceptionDescriptor(descriptor, invocationDescriptor); Class<? extends Exception> resolved = exceptionResolver.resolve(exceptionDescriptor, e); if (resolved != null) { ExceptionSeverity excSeverity = exceptionDescriptor.getExceptionSeverity(resolved); if (isLoggingOn(excSeverity.getSeverity(), logger)) { logStrategies.get(excSeverity.getSeverity()).logException(logger, methodName, args.length, e, excSeverity.getStackTrace()); } } throw e; } } if (afterLoggingOn(invocationDescriptor, logger)) { Object loggedResult = (method.getReturnType() == Void.TYPE) ? Void.TYPE : result; logStrategies.get(invocationDescriptor.getAfterSeverity()).logAfter(logger, methodName, args.length, loggedResult); } return result; } private MethodDescriptor getMethodDescriptor(Method method) { MethodDescriptor cached = cache.get(method); if (cached != null) { return cached; } cached = new MethodDescriptor(new InvocationDescriptor.Builder(method).build()); MethodDescriptor prev = cache.putIfAbsent(method, cached); return prev == null ? cached : prev; } private ArgumentDescriptor getArgumentDescriptor(MethodDescriptor descriptor, Method method, int argumentCount) { if (descriptor.getArgumentDescriptor() != null) { return descriptor.getArgumentDescriptor(); } ArgumentDescriptor argumentDescriptor = new ArgumentDescriptor.Builder(method, argumentCount, localVariableNameDiscoverer).build(); descriptor.setArgumentDescriptor(argumentDescriptor); return argumentDescriptor; } private ExceptionDescriptor getExceptionDescriptor(MethodDescriptor descriptor, InvocationDescriptor invocationDescriptor) { if (descriptor.getExceptionDescriptor() != null) { return descriptor.getExceptionDescriptor(); } ExceptionDescriptor exceptionDescriptor = new ExceptionDescriptor.Builder(invocationDescriptor.getExceptionAnnotation()).build(); descriptor.setExceptionDescriptor(exceptionDescriptor); return exceptionDescriptor; } private Method extractMethod(ProceedingJoinPoint joinPoint) throws NoSuchMethodException { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // signature.getMethod() points to method declared in interface. it is not suit to discover arg names and arg annotations // see AopProxyUtils: org.springframework.cache.interceptor.CacheAspectSupport#execute(CacheAspectSupport.Invoker, Object, Method, Object[]) Class<?> targetClass = joinPoint.getTarget().getClass(); if (Modifier.isPublic(signature.getMethod().getModifiers())) { return targetClass.getMethod(signature.getName(), signature.getParameterTypes()); } else { return ReflectionUtils.findMethod(targetClass, signature.getName(), signature.getParameterTypes()); } } private boolean beforeLoggingOn(InvocationDescriptor descriptor, Log logger) { return isLoggingOn(descriptor.getBeforeSeverity(), logger); } private boolean afterLoggingOn(InvocationDescriptor descriptor, Log logger) { return isLoggingOn(descriptor.getAfterSeverity(), logger); } private boolean isLoggingOn(Severity severity, Log logger) { return severity != null && logStrategies.get(severity).isLogEnabled(logger); } }